V2: Configurability and formatting options#197
Conversation
Fixes #139
Custom i18n mechanism
|
No large file size changes found Generated by |
|
This test fails by falsely outputting an approximation word: assertThat(
duration(
duration = 1.days + 1.hours + 4.minutes,
parts = PartsConfig(max = 5),
formatting = FormatStyle(indicateApproximation = true),
)
).isEqualTo("1 day, 1 hour, 4 minutes")outputs: |
Thanks! Fixed in 49cbe51 |
068285d to
790e5b8
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces the v2 relative-time/duration formatting engine with extensive configurability (format styles, multi-part output, cutoffs, unit-limiting, rounding strategies, and date-based “today/tomorrow/yesterday”), and replaces the previous Libres-based localisation with an internal i18n implementation across supported platforms (including new tvOS/watchOS targets).
Changes:
- Added configurable time formatting primitives (
FormatStyle,PartsConfig,Rounding, newTimeUnit) and rewiredHumanReadable.timeAgo()/duration()to use them (including aLocalDateoverload). - Migrated localisation from Libres XML resources to internal Kotlin translation objects + platform
systemLanguageTag()implementations. - Updated README, demo app UI, and build tooling/targets (tvOS/watchOS, additional native targets, Gradle/Compose updates).
Reviewed changes
Copilot reviewed 127 out of 128 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| src/webMain/kotlin/nl/jacobras/humanreadable/i18n/systemLanguageTag.web.kt | Web implementation of systemLanguageTag() via browser navigator language. |
| src/mingwMain/kotlin/nl/jacobras/humanreadable/i18n/systemLanguageTag.mingw.kt | Windows (mingw) implementation of systemLanguageTag() via Win32 locale APIs. |
| src/linuxMain/kotlin/nl/jacobras/humanreadable/i18n/systemLanguageTag.linux.kt | Linux implementation of systemLanguageTag() via environment variables. |
| src/jvmTest/kotlin/nl/jacobras/humanreadable/i18n/LocalisationTest.kt | JVM tests for localisation selection, fallback, and normalization. |
| src/jvmMain/kotlin/nl/jacobras/humanreadable/i18n/systemLanguageTag.jvm.kt | JVM implementation of systemLanguageTag() from Locale. |
| src/jvmMain/kotlin/nl/jacobras/humanreadable/extendLibresPlurals.kt | Removes JVM Libres plural workaround (Libres migration). |
| src/commonTest/kotlin/nl/jacobras/humanreadable/time/HumanReadableRelativeTimeTests.kt | New common tests for v2 relative time behaviors (incl. today/tomorrow/yesterday). |
| src/commonTest/kotlin/nl/jacobras/humanreadable/HumanReadableRelativeTimeTests.kt | Removes old relative time tests that depended on Libres. |
| src/commonTest/kotlin/nl/jacobras/humanreadable/HumanReadableNumberEdgeCaseTests.kt | Updates tests to use new HumanReadable.config.languageTag. |
| src/commonTest/kotlin/nl/jacobras/humanreadable/HumanReadableFileSizeTests.kt | Updates tests to use new HumanReadable.config.languageTag. |
| src/commonTest/kotlin/nl/jacobras/humanreadable/HumanReadableDurationTests.kt | Removes old duration tests (v1 formatting logic). |
| src/commonTest/kotlin/nl/jacobras/humanreadable/HumanReadableAbbreviationTests.kt | Updates tests to use new HumanReadable.config.languageTag. |
| src/commonMain/libres/strings/time_units_ar.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_cs.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_de.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_el.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_en.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_es.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_fi.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_fr.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_id.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_it.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_ja.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_kk.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_ko.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_nl.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_pl.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_pt.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_ru.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_tr.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_uk.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_uz.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_vi.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/time_units_zh.xml | Removes Libres XML time strings (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_cs.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_de.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_el.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_en.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_es.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_fi.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_fr.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_id.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_it.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_nl.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_pl.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_pt.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_ru.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_tr.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_uk.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_uz.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/number_units_vi.xml | Removes Libres XML number symbols (migration to Kotlin translations). |
| src/commonMain/libres/strings/distance_units_en.xml | Removes Libres XML distance unit abbreviations (migration). |
| src/commonMain/libres/strings/distance_units_ar.xml | Removes Libres XML distance unit abbreviations (migration). |
| src/commonMain/libres/strings/data_units_en.xml | Removes Libres XML file-size unit symbols (migration). |
| src/commonMain/libres/strings/data_units_ar.xml | Removes Libres XML file-size unit symbols (migration). |
| src/commonMain/libres/strings/data_units_fi.xml | Removes Libres XML file-size unit symbols (migration). |
| src/commonMain/libres/strings/data_units_fr.xml | Removes Libres XML file-size unit symbols (migration). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/TimeUnit.kt | Removes old Libres-backed TimeUnit (moved/refactored). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/time/TimeUnit.kt | New public TimeUnit with style-dependent tense/plural forms and rounding hooks. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/time/Rounding.kt | Adds rounding strategies (Floor, HalfUp, IfClose) for time formatting. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/time/RelativeTime.kt | Moves RelativeTime into the time package. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/time/PartsConfig.kt | Adds multi-part duration formatting configuration (max parts, cutoffs, etc.). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/time/HumanReadableRelativeTime.kt | New relative-time formatter for Instant and LocalDate including special day words. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/time/HumanReadableDuration.kt | New duration formatter with parts, unit-limiting, approximation/“less than”, and digital time. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/time/FormatStyle.kt | Adds date/time format styles and approximation indication options. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/SafelyTranslate.kt | Removes Libres-era translation crash guard helpers. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/ArStrings.kt | Arabic Kotlin translations (time + file size + distance). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/CsStrings.kt | Czech Kotlin translations (time + number). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/DeStrings.kt | German Kotlin translations (time + number). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/ElStrings.kt | Greek Kotlin translations (time + number). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/EnStrings.kt | English Kotlin translations (default baseline). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/EsStrings.kt | Spanish Kotlin translations (time + number). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/FiStrings.kt | Finnish Kotlin translations (time + number + file size). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/FrStrings.kt | French Kotlin translations (time + number + file size). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/IdStrings.kt | Indonesian Kotlin translations (time + number). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/ItStrings.kt | Italian Kotlin translations (time + number). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/JaStrings.kt | Japanese Kotlin translations (time). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/KkStrings.kt | Kazakh Kotlin translations (time). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/KoStrings.kt | Korean Kotlin translations (time). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/NlStrings.kt | Dutch Kotlin translations (time + number). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/PlStrings.kt | Polish Kotlin translations (time + number). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/PtStrings.kt | Portuguese Kotlin translations (time + number). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/RuStrings.kt | Russian Kotlin translations (time + number). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/TrStrings.kt | Turkish Kotlin translations (time + number). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/UkStrings.kt | Ukrainian Kotlin translations (time + number). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/UzStrings.kt | Uzbek Kotlin translations (time + number). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/ViStrings.kt | Vietnamese Kotlin translations (time + number). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/ZhStrings.kt | Chinese Kotlin translations (time). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations.kt | Registers supported languages in a translation map. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/TenseForms.kt | Adds plural/tense forms model + DSL for translation authoring. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/systemLanguageTag.kt | Declares expected systemLanguageTag() for platform implementations. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/Plural.kt | Defines CLDR plural category enum. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/Localisation.kt | Implements language selection, normalization, caching, and fallback behavior. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/HumanReadableStrings.kt | Defines the structured i18n string model (time/number/file size/distance). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/HumanReadableRelativeTime.kt | Removes old Libres-based relative time formatter. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/HumanReadableNumber.kt | Rewires number formatting to internal i18n symbols. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/HumanReadableFileSize.kt | Rewires file size symbols to internal i18n strings. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/HumanReadableDuration.kt | Removes old duration formatter (replaced by v2 time module). |
| src/commonMain/kotlin/nl/jacobras/humanreadable/HumanReadableDistance.kt | Rewires distance unit abbreviations to internal i18n strings. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/HumanReadable.kt | Public API updates: new config, new overloads, v2 time formatting parameters. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/extendLibresPlurals.kt | Removes Libres plural workaround expect declaration. |
| src/commonMain/kotlin/nl/jacobras/humanreadable/Config.kt | Adds global configuration container (HumanReadable.config) for language/time defaults. |
| src/appleMain/kotlin/nl/jacobras/humanreadable/i18n/systemLanguageTag.apple.kt | Apple implementation of systemLanguageTag(). |
| src/appleAndJsMain/kotlin/nl/jacobras/humanreadable/extendLibresPlurals.kt | Removes Apple/JS Libres plural workaround implementation. |
| README.md | Updates documentation for v2 formatting options and new i18n API. |
| karma.config.d/index.js | Removes old Karma workaround script file. |
| gradle/wrapper/gradle-wrapper.properties | Updates Gradle wrapper settings/version and wrapper retry config. |
| gradle/libs.versions.toml | Updates Compose/KMP dependencies; adds adaptive + browser libraries and buildConfig plugin. |
| gradle.properties | Adds project version property and keeps jscanvas flag. |
| demo/src/webMain/kotlin/ui/TimeDemo.kt | Removes old demo time screen implementation. |
| demo/src/webMain/kotlin/ui/highlightKotlinCode.kt | Adds Kotlin code highlighting utility for demo output. |
| demo/src/webMain/kotlin/ui/FlexibleLayout.kt | Removes old flexible multi-demo layout. |
| demo/src/webMain/kotlin/ui/FeaturesList.kt | Adds feature navigation list for demo scaffold. |
| demo/src/webMain/kotlin/ui/DistanceDemo.kt | Removes old distance demo implementation (migrated to feature screens). |
| demo/src/webMain/kotlin/ui/DecimalsInput.kt | Adds reusable decimals input control for demo screens. |
| demo/src/webMain/kotlin/ui/CodeExample.kt | Adds reusable code+result display component (selectable) for demo. |
| demo/src/webMain/kotlin/ui/AbbreviationDemo.kt | Removes old abbreviation demo implementation. |
| demo/src/webMain/kotlin/feature/TimeDemo.kt | Adds new time demo feature screen showcasing configuration options. |
| demo/src/webMain/kotlin/feature/NumberFormatDemo.kt | Moves/updates number demo to feature module + shared UI components. |
| demo/src/webMain/kotlin/feature/FileSizeDemo.kt | Moves/updates file size demo to feature module + shared UI components. |
| demo/src/webMain/kotlin/feature/DistanceDemo.kt | Adds new distance demo feature screen using shared UI components. |
| demo/src/webMain/kotlin/Feature.kt | Adds demo feature enum for navigation. |
| demo/src/webMain/kotlin/App.kt | Updates demo app scaffold, language switching, adaptive navigation, and version display. |
| demo/build.gradle.kts | Adds BuildConfig generation and adaptive Compose dependencies for demo. |
| build.gradle.kts | Removes Libres plugin config, adds new targets, adds web deps, and reads version from property. |
| .github/workflows/build.yml | Minor workflow cleanup (comment removal). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| today = "اليوم", | ||
| tomorrow = "أمس", | ||
| yesterday = "غداً", |
| var requestedLanguageTag = systemLanguageTag() | ||
| set(value) { | ||
| field = value.lowercase().substringBefore("-") | ||
| currentTagAndStrings = null | ||
| } |
| baseDate -> return strings.dateTime.today | ||
| baseDate.plus(1, DateTimeUnit.DAY) -> return strings.dateTime.tomorrow | ||
| } | ||
| val secondsAgo = date.daysUntil(baseDate) * 86_400 |
| if (format.time == FormatStyle.Time.Digital) { | ||
| if (!isEmpty()) append(", ") | ||
|
|
||
| val hours = (duration.inWholeHours % 24).toString().padStart(2, '0') | ||
| val minutes = (duration.inWholeMinutes % 60).toString().padStart(2, '0') | ||
| val seconds = (duration.inWholeSeconds % 60).toString().padStart(2, '0') | ||
| append("$hours:$minutes:$seconds") |
| parts = PartsConfig( | ||
| max = 2, // "1 hour, 50 minutes" | ||
| smallestDuration = 10.minutes, // anything smaller will return "less than 10 minutes" | ||
| subpartCutOffs = mapOf(TimeUnit.Hours to 12.hours), // drops subparts, e.g. "11 hours, 40 minutes" and then "12 hours" |
| subpartCutOffs = mapOf(TimeUnit.Hours to 12.hours), // drops subparts, e.g. "11 hours, 40 minutes" and then "12 hours" | ||
| onlyConsecutiveParts = true // whether "1 hour, 5 seconds" can be returned (as seconds are not the next unit after hours) | ||
| ), | ||
| units = setOf(TimeUnit.Hours), // limits the output to these units, e.g. "391 days" |
| CodeExample( | ||
| code = "rounding = Rounding.UpIfClose", | ||
| res = remember(selectedLanguageCode, now, myInstant) { | ||
| HumanReadable.timeAgo( | ||
| myInstant, now, | ||
| rounding = Rounding.IfClose() | ||
| ) | ||
| }, | ||
| inline = true | ||
| ) |
| * - `1.minutes + 5.seconds` remains "1 minute, 5 seconds"; | ||
| * - `1.minutes + 4.seconds` gets rounded to "1 minute"; | ||
| * - `1.minutes + 54 seconds` remains "1 minute, 54 seconds"; | ||
| * - `1.minutes + 55 seconds` gets rounded to "2 minutes". |
| onlyConsecutiveParts = true // whether "1 hour, 5 seconds" can be returned (as seconds are not the next unit after hours) | ||
| ), | ||
| units = setOf(TimeUnit.Hours), // limits the output to these units, e.g. "391 days" | ||
| rounding = Rounding.HalfUp // or Floor to round down, or UpIfClose to round up on 55 seconds/55 minutes/23 hours/13 days |
V2
The biggest update to the library yet. All changes are in the time section. Backwards compatible with v1, but with a slight behavioural fix: default rounding
HalfUpis now consistent.Long: "1 hour, 50 minutes"Short: "1 hr, 50 min"Narrow: "1h 50m"Regular: "1 hour, 50 minutes"Digital: "01:50:00HalfUp(default)FloorUpIfClose(rounds up on 55 seconds/55 minutes/23 hours/13 days):app:minifyReleaseWithR8#177See updated README in this branch for the exact parameter names: https://github.com/jacobras/Human-Readable/blob/feature/2.0-configurability-and-formatting/README.md (or take a look at the tests)
DEMO
A live demo of 2.0.0 is available at https://www.jacobras.nl/humanreadable2/index.html (⚠️ not latest state)
TODO